Recall from the previous chapter that C# does allow you to define generic delegate types. For example, assume you wish to define a delegate type that can call any method returning void and receiving a single parameter. If the argument in question may differ, you could model this using a type parameter. To illustrate, consider the following code within a new Console Application named GenericDelegate:
namespace GenericDelegate { // This generic delegate can call any method // returning void and taking a single type parameter. public delegate void MyGenericDelegate<T>(T arg); class Program { static void Main(string[] args) { Console.WriteLine("***** Generic Delegates *****\n"); // Register targets. MyGenericDelegate<string> strTarget = new MyGenericDelegate<string>(StringTarget); strTarget("Some string data"); MyGenericDelegate<int> intTarget = new MyGenericDelegate<int>(IntTarget); intTarget(9); Console.ReadLine(); } static void StringTarget(string arg) { Console.WriteLine("arg in uppercase is: {0}", arg.ToUpper()); } static void IntTarget(int arg) { Console.WriteLine("++arg is: {0}", ++arg); } } }
Notice that MyGenericDelegate<T> defines a single type parameter that represents the argument to pass to the delegate target. When creating an instance of this type, you are required to specify the value of the type parameter as well as the name of the method the delegate will invoke. Thus, if you specified a string type, you send a string value to the target method:
// Create an instance of MyGenericDelegate<T> // with string as the type parameter. MyGenericDelegate<string> strTarget = new MyGenericDelegate<string>(StringTarget); strTarget("Some string data");
Given the format of the strTarget object, the StringTarget() method must now take a single string as a parameter:
static void StringTarget(string arg) { Console.WriteLine("arg in uppercase is: {0}", arg.ToUpper()); }
Generic delegates offer a more flexible way to specify the method to be invoked in a type-safe manner. Before the introduction of generics in .NET 2.0, you could achieve a similar end result using a System.Object parameter:
public delegate void MyDelegate(object arg);
Although this allows you to send any type of data to a delegate target, you do so without type safety and with possible boxing penalties. For instance, assume you have created two instances of MyDelegate, both of which point to the same method, MyTarget. Note the boxing/unboxing penalties as well as the inherent lack of type safety:
class Program { static void Main(string[] args) { ... // Register target with "traditional" delegate syntax. MyDelegate d = new MyDelegate(MyTarget); d("More string data"); // Method group conversion syntax. MyDelegate d2 = MyTarget; d2(9); // Boxing penalty. Console.ReadLine(); } // Due to a lack of type safety, we must // determine the underlying type before casting. static void MyTarget(object arg) { if(arg is int) { int i = (int)arg; // Unboxing penalty. Console.WriteLine("++arg is: {0}", ++i); } if(arg is string) { string s = (string)arg; Console.WriteLine("arg in uppercase is: {0}", s.ToUpper()); } } }
When you send out a value type to the target site, the value is boxed and unboxed once it is received by the target method. As well, given that the incoming parameter could be anything at all, you must dynamically check the underlying type before casting. Using generic delegates, you can still obtain the desired flexibility without the issues.
Source Code The GenericDelegate project is located under the Chapter 11 directory.
That wraps up our first look at the .NET delegate type. We will look at some additional details of working with delegates at the conclusion of this chapter and again in Chapter 19 during our examination of multithreading. Now let’s move on to the related topic of the C# event keyword.